﻿using Digitalmes.Application;
using Digitalmes.Common.Configs;
using Digitalmes.Common.Extensions;
using Digitalmes.Common.Result;
using Digitalmes.Common.Utils;

namespace Digitalmes.ApiService.Filters;

/// <summary>
/// 对请求的API进行安全签名认证校验筛选器
/// </summary>
public sealed class CheckApiSecurityFilter : IAsyncActionFilter
{
    private readonly ILogger _logger;

    public CheckApiSecurityFilter(ILogger<CheckApiSecurityFilter> logger)
    {
        _logger = logger;
    }

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // "/api/sysfile/", "/api/exammaterial/upload", "/api/captcha", "/chathub" 等不做安全签名认证校验

        var request = context.HttpContext.Request;

        bool checkSecurity = true;
        if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
        {
            checkSecurity = !descriptor.ControllerTypeInfo.IsDefined(typeof(NoApiSecurityAttribute), true)
                && !descriptor.MethodInfo.IsDefined(typeof(NoApiSecurityAttribute), true);
        }

        if (!checkSecurity)
        {
            await next();
        }

        var security = AppUtils.GetConfig<Security>(Security.Name);
        var signKey = security.SignKey; // 系统配置的签名key

        // 客户的唯一标识
        if (!request.Headers.TryGetValue("appkey", out var appkey) || appkey != signKey)
        {
            _logger.LogInformation("{Api}, ApiSecurity —— 请求不合法 -- signKey", request.Path);
            context.Result = new JsonResult(JResult.Error("参数安全签名不合法-signKey"));
            return;
        }

        // 13位时间戳
        if (!request.Headers.TryGetValue("timestamp", out var timestamp) || double.TryParse(timestamp, out var ts1))
        {
            _logger.LogInformation("{Api}, ApiSecurity —— 请求不合法 -- timestamp", request.Path);
            context.Result = new JsonResult(JResult.Error("参数安全签名不合法-timestamp"));
            return;
        }

        // 签名
        if (!request.Headers.TryGetValue("signature", out var signature))
        {
            _logger.LogInformation("{Api}, ApiSecurity —— 请求不合法 -- signature", request.Path);
            context.Result = new JsonResult(JResult.Error("参数安全签名不合法-signature"));
            return;
        }

        // 校验是否已过期
        double ts2 = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        var expire = (ts2 - ts1) > 60_000; // 1分钟有效
        if (expire)
        {
            _logger.LogInformation("{Api}, ApiSecurity —— 请求不合法 -- expire", request.Path);
            context.Result = new JsonResult(JResult.Error("参数安全签名认证校验不通过-expire"));
            return;
        }

        // 根据请求类型拼接参数
        Dictionary<string, string> parameters = new();
        string data = string.Empty;
        switch (request.Method)
        {
            case "POST" or "PUT" or "DELETE":
                context.HttpContext.Request.Body.Position = 0;
                StreamReader stream = new(context.HttpContext.Request.Body);
                data = await stream.ReadToEndAsync();
                context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
                break;
            case "GET":
                {
                    var query = request.Query;
                    foreach (var item in query)
                    {
                        parameters.Add(item.Key, item.Value!);
                    }

                    // 把字典按Key的字母顺序排序
                    SortedDictionary<string, string> sortedParams = new(parameters);
                    using IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();

                    // 把所有参数名和参数值串在一起
                    StringBuilder stringBuilder = new();
                    while (dem.MoveNext())
                    {
                        string key = dem.Current.Key;
                        string value = dem.Current.Value;
                        if (!string.IsNullOrEmpty(key))
                        {
                            stringBuilder.Append(key).Append(value);
                        }
                    }

                    data = stringBuilder.ToString();
                    break;
                }
        }

        var signStr = $"{appkey}{signKey}{timestamp}{data}";  // 拼接签名数据
        var newSign = signStr.MDString();

        if (newSign != signature)
        {
            _logger.LogInformation("{Api}, ApiSecurity -- 参数安全签名认证校验失败 -- sign", request.Path);
            context.Result = new JsonResult(JResult.Error("参数安全签名认证校验不通过"));
            return;
        }

        await next();
    }
}
